package jmine.tec.persist.hibernate.proxy;

import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import jmine.tec.utils.spring.BeanReference;

import org.hibernate.engine.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;

/**
 * Encapsula uma {@link HibernateProxy} para fazê-la nunca perder a sessão possibilitando serialização da mesma.
 * 
 * @author wellington.santos
 * @created May 30, 2012
 */
public abstract class AbstractPojoProxy extends AbstractPersistentProxy<HibernateProxy> {

    private static final long serialVersionUID = -6587165518767397176L;

    private static final Class<?>[] HIBERNATE_PROXY_CLASS_ARRAY = { HibernateProxy.class, SerializableLazyProxy.class };

    private final HibernateProxy impl;

    private transient boolean initialized;

    /**
     * Construtor.
     *
     * @param sessionReference sessão do hibernate
     * @param impl objeto lazy a encapsular
     */
    public AbstractPojoProxy(BeanReference<? extends SessionImplementor> sessionReference, HibernateProxy impl) {
        super(sessionReference);
        this.impl = impl;
        this.initialized = !impl.getHibernateLazyInitializer().isUninitialized();
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    public HibernateProxy getLazyObject() {
        return this.impl;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public boolean isUninitialized() {
        return this.getLazyObject().getHibernateLazyInitializer().isUninitialized();
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    protected ProxyObject createProxy() {
        ProxyFactory factory = new ProxyFactory();
        factory.setSuperclass(this.impl.getHibernateLazyInitializer().getPersistentClass());
        factory.setInterfaces(HIBERNATE_PROXY_CLASS_ARRAY);
        factory.setFilter(FinalizeFilter.INSTANCE);
        Class<?> resultClass = factory.createClass();
        try {
            ProxyObject resultObject = (ProxyObject) resultClass.newInstance();
            resultObject.setHandler(this);
            return resultObject;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    public Object getImplementation() {
        this.restoreSession();
        Object result = this.impl.getHibernateLazyInitializer().getImplementation();
        this.checkInitialization();
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void restoreSession() {
        LazyInitializer hproxy = this.impl.getHibernateLazyInitializer();
        if (hproxy.isUninitialized()) {
            SessionImplementor oldSession = hproxy.getSession();
            if (oldSession == null || oldSession.isClosed()) {
                SessionImplementor newSession = this.getSessionImplementor();
                if (oldSession != newSession) {
                    hproxy.setSession(newSession);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    protected boolean checkInitialization() {
        if (!this.initialized && !this.impl.getHibernateLazyInitializer().isUninitialized()) {
            this.initialized = true;
            this.onInitialized(this.impl.getHibernateLazyInitializer().getImplementation());
            return true;
        }
        return false;
    }

}
